<?php

    /**
     * Debug
     *
     * Error and exception handlers
     *
     * @package      Core
     * @subpackage   Debug
     * 
     * @license      GNU Lesser General Public Licence see LICENCE-LGPL file or http://www.gnu.org/licenses/lgpl.html
     */
    final class Core_Debug
    {

	////////////////////////////////////////////////////////////////////////////
	//                                Variables                               //
	////////////////////////////////////////////////////////////////////////////

        /**
         * Singleton instance
         *
         * Marked only as protected to allow extension of the class.
         * To extend, simply override {@link getInstance()}.
         *
         * @var Core_Debug
         */
        protected static $_instance = null;

		/**
		 * Core_Config object
		 *
		 * @var Core_Config
		 */
		private $_config = null;

        /**
         * The application coordinator
         *
         * @var Core_Controller_Coordinator
         */
        private $_coordinator = null;

	////////////////////////////////////////////////////////////////////////////
	//                             Static Methods                             //
	////////////////////////////////////////////////////////////////////////////

        /**
         * Singleton instance
         *
         * @param		Core_Controller_Coordinator		$coordinator : the current application
         * @return      Core_Debug
         */
        public static function getInstance(Core_Controller_Coordinator $coordinator)
        {
            if (self::$_instance === null) {
                self::$_instance = new self($coordinator);
            }

            return self::$_instance;
        }

	////////////////////////////////////////////////////////////////////////////
	//                                 Methods                                //
	////////////////////////////////////////////////////////////////////////////


        /**
         * Constructor
         *
         * @param		Core_Controller_Coordinator		$coordinator : the current application
         */
        public function __construct(Core_Controller_Coordinator $coordinator)
		{
			$registry = Zend_Registry::getInstance();
			$this->_config = $registry['config'];

			$this->_coordinator = $coordinator;
        }

		/**
		 * Destructor
		 */
		public function __destruct()
		{
			unset($this);
		}

        /**
        * Error handler for the framework
        * Replaces the default PHP error handler
        *
        * This function was originally from the Copix project (CopixErrorHandler, Copix 2.3dev20050901, http://www.copix.org)
        * Few lines of code are still copyrighted 2001-2005 CopixTeam (LGPL licence).
        * Initial authors of this function are Gerald Croes and Laurent Jouanneau,
        * and it was adapted/improved for Jelix by Laurent Jouanneau
        *
        * @param        integer     $errno : error code
        * @param        string      $errmsg : error message
        * @param        string      $filename : filename where the error appears
        * @param        integer     $linenum : line number where the error appears
        * @param        array       $errcontext
        * @return       boolean     The error handler must return false to populate $php_errormsg, since PHP 5.2.0
        */
        public function handlerError($errno, $errmsg, $filename, $linenum, $errcontext)
        {
            if (error_reporting() == 0) {
                return false;
            }

            $codeString = array(
                E_ERROR             => 'error',
                E_RECOVERABLE_ERROR => 'error',
                E_WARNING           => 'warning',
                E_NOTICE            => 'notice',
                E_USER_ERROR        => 'error',
                E_USER_WARNING      => 'warning',
                E_USER_NOTICE       => 'notice',
                E_STRICT            => 'strict'
            );

            if (preg_match('/^\s*\((\d+)\)(.+)$/', $errmsg, $m)) {
                $code = $m[1];
                $errmsg = $m[2];
            } else {
                $code = 1;
            }

            if (isset($codeString[$errno])) {
                $action = $this->_config->error->keywords->{$codeString[$errno]};
            } else {
                $action = $this->_config->error->keywords->default;
            }

            $doEchoByResponse = true;

            if ($this->_coordinator->getRequest() == null) {
                $errmsg = 'Error during initialization !! '. $errmsg;
                $doEchoByResponse = false;
                $action .= ' EXIT';
            } else if ($this->_coordinator->getResponse() == null) {
                try {
                    $this->_coordinator->setDefaultResponse();
                } catch (Exception $e) {
                    $errmsg = 'Double error ! 1) '. $e->getMessage() .'; 2) '. $errmsg;
                    $doEchoByResponse = false;
                }
            }

            // When we are in cmdline we need to fix the remoteAddr
            $remoteAddr = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '127.0.0.1';

            $messageLog = strtr($this->_config->error->messageLogFormat, array(
                '%date%'        => date("Y-m-d H:i:s"),
                '%ip%'          => $remoteAddr,
                '%typeerror%'   => $codeString[$errno],
                '%code%'        => $code,
                '%msg%'         => $errmsg,
                '%file%'        => $filename,
                '%line%'        => $linenum,
                '\t'            => "\t",
                '\n'            => "\n"
            ));

            if (strpos($action , 'TRACE') !== false) {
                $arr = @debug_backtrace();
                $messageLog .= "\ttrace:";
                array_shift($arr);

                foreach ($arr as $k => $t) {
                    $messageLog .= "\n\t". $k ."\t". (isset($t['class']) ? $t['class'] . $t['type'] : '') . $t['function'] . "()\t";
                    $messageLog .= (isset($t['file']) ? $t['file'] : '[php]') .' : '. (isset($t['line']) ? $t['line'] : '');
                }

                $messageLog.="\n";
            }

            $echoAsked = false;

            if (strpos($action, 'ECHOQUIET') !== false) {
                $echoAsked = true;
                if (!$doEchoByResponse) {
                    header("HTTP/1.1 500 Internal error");
                    header('Content-type: text/plain');
                    echo 'Error during initialization !!';
                } else if ($this->_coordinator->addErrorMessage($codeString[$errno], $code, $this->_config->error->quietMessage, '', '')) {
                    $action .= ' EXIT';
                }
            } else if (strpos($action, 'ECHO') !== false) {
                $echoAsked = true;
                if (!$doEchoByResponse) {
                    header("HTTP/1.1 500 Internal error");
                    header('Content-type: text/plain');
                    echo $messageLog;
                } else if ($this->_coordinator->addErrorMessage($codeString[$errno], $code, $errmsg, $filename, $linenum)) {
                    $action .= ' EXIT';
                }
            }

            if (strpos($action, 'LOGFILE') !== false) {
                @error_log($messageLog, 3, PROJECT_LOG_PATH . $this->_config->error->logFile);
            }

            if (strpos($action, 'MAIL') !== false) {
                @error_log(wordwrap($messageLog, 70), 1, $this->_config->error->email, $this->_config->error->emailHeaders);
            }

            if (strpos($action, 'SYSLOG') !== false) {
                @error_log($messageLog, 0);
            }

            if(strpos($action, 'EXIT') !== false) {
                if ($doEchoByResponse) {
                    if ($this->_coordinator->getResponse()) {
                        $this->_coordinator->getResponse()->outputErrors();
                    } else if ($echoAsked) {
                        header("HTTP/1.1 500 Internal error");
                        header('Content-type: text/plain');
                        foreach ($this->_coordinator->getErrorMessages() as $msg) {
                            echo $msg ."\n";
                        }
                    }
                }

                Zend_Session::stop();
                exit(1);
            }

            return true;
        }

        /**
        * Exception handler for the framework
        * Replaces the default PHP Exception handler
        *
        * @param        Exception       $e : the exception object
        */
        public function handleException(Exception $e)
        {
            $doEchoByResponse = true;
			$message = $e->getMessage();
            $action = $this->_config->error->keywords->exception;

            if ($this->_coordinator->getRequest() == null) {
                $message = 'Error during initialization !! '. $message;
                $doEchoByResponse = false;
            } else if ($this->_coordinator->getResponse() == null) {
                try {
                    $this->_coordinator->setDefaultResponse();
                } catch (Exception $e) {
                    $errmsg = 'Double error ! 1) '. $e->getMessage() .'; 2) '. $errmsg;
                    $doEchoByResponse = false;
                }
            }

            // When we are in cmdline we need to fix the remoteAddr
            $remoteAddr = isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '';

            $messageLog = strtr($this->_config->error->messageLogFormat, array(
                '%date%'        => date("Y-m-d H:i:s"),
                '%ip%'          => $remoteAddr,
                '%typeerror%'   => 'exception',
                '%code%'        => $e->getCode(),
                '%msg%'         => $message,
                '%file%'        => $e->getFile(),
                '%line%'        => $e->getLine(),
                '\t'            => "\t",
                '\n'            => "\n"
            ));

            if (strpos($action, 'TRACE') !== false) {
                $arr = $e->getTrace();
                $messageLog .= "\ttrace:";

                foreach ($arr as $k => $t) {
                    $messageLog .= "\n\t". $k ."\t". (isset($t['class']) ? $t['class'] . $t['type'] : '') . $t['function'] ."()\t";
                    $messageLog .= (isset($t['file']) ? $t['file'] : '[php]') .' : '. (isset($t['line']) ? $t['line'] : '');
                }

                $messageLog .= "\n";
            }

            $echoAsked = false;

            if (strpos($action, 'ECHOQUIET') !== false) {
                $echoAsked = true;
                if (!$doEchoByResponse) {
                    header("HTTP/1.1 500 Internal error");
                    header('Content-type: text/plain');
                    echo 'Error during initialization !!';
                } else {
                    $this->_coordinator->addErrorMessage('error', $e->getCode(), $this->_config->error->quietMessage, '', '');
                }
            } else if (strpos($action, 'ECHO') !== false) {
                $echoAsked = true;
                if ($doEchoByResponse) {
                    $this->_coordinator->addErrorMessage('error', $e->getCode(), $message, $e->getFile(), $e->getLine());
                } else {
                    header("HTTP/1.1 500 Internal error");
                    header('Content-type: text/plain');
                    echo $messageLog;
                }
            }

            if (strpos($action, 'LOGFILE') !== false) {
                @error_log($messageLog, 3, PROJECT_LOG_PATH . $this->_config->error->logFile);
            }

            if (strpos($action, 'MAIL') !== false) {
                @error_log(wordwrap($messageLog, 70), 1, $this->_config->error->email, $this->_config->error->emailHeaders);
            }

            if (strpos($action, 'SYSLOG') !== false) {
                @error_log($messageLog, 0);
            }

            if ($doEchoByResponse) {
                if ($this->_coordinator->getResponse()) {
                    $this->_coordinator->getResponse()->outputErrors();
                } else if ($echoAsked) {
                    header("HTTP/1.1 500 Internal error");
                    header('Content-type: text/plain');
                    foreach ($this->_coordinator->getErrorMessages() as $msg) {
                        echo $msg ."\n";
                    }
                }
            }

            Zend_Session::stop();
            exit(1);
        }
    }
